package com.example.ssyinitlearn.config;

import com.alibaba.fastjson.JSON;
import com.example.ssyinitlearn.authenticationProvider.MyDaoAuthenticationProvider;
import com.example.ssyinitlearn.filter.JsonLoginFilter;
import com.example.ssyinitlearn.userDetailsServiceImpl.InMemoryServiceImpl_1;
import com.example.ssyinitlearn.userDetailsServiceImpl.UserEntityServiceImpl;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.http.MediaType;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.OrRequestMatcher;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.util.ArrayList;
import java.util.HashMap;

/**
 * SSY配置文件。
 */
@Order(1)
@Configuration
public class DoLogin_1_Config extends WebSecurityConfigurerAdapter {
    /**
     * configure 1.1
     * 继承的 WebSecurityConfigurerAdapter 类中有很多方法，这里直接覆写 configure()
     *
     * @param http
     * @throws Exception
     */
//    @Override
//    protected void configure(HttpSecurity http) throws Exception {
//        // .authorizeRequests()开启权限配置
//        http.authorizeRequests()
//                // 对所有的请求都要进行认证
//                .antMatchers("/vcode.jpg").permitAll()
//                .anyRequest().authenticated();
//
//        // .formLogin()开启登录表单配置
//        // 还有一种简单的写法，那就是直接在上面使用 .and().formLogin 以链式来进行书写
//        http.formLogin()
//                // 指定进行认证的登录页面
//                .loginPage("/login1.html")
//                // 进行登录认证处理的路径
//                .loginProcessingUrl("/doLogin1")
//                // 直连登录页面成功后的响应地址
//                .defaultSuccessUrl("/test2")
//                // 登录失败之后返回哪里？
//                .failureUrl("/login1.html")
//                // 登录页面传递的username和password变量的名字是啥
//                .usernameParameter("uname")
//                .passwordParameter("passwd")
//                // 与登录相关的页面和接口不做拦截，直接通过
//                .permitAll();
//
//        // 关闭CSRF防御功能。当然依然可以用 .and() 来写
//        http.csrf()
//                .disable();
//    }

    /**
     * configure 1.2
     * 使用自定义 SavedRequestAwareAuthenticationSuccessHandler
     * @param http
     * @throws Exception
     */
//    @Override
//    protected void configure(HttpSecurity http) throws Exception {
//        // .authorizeRequests()开启权限配置
//        http.authorizeRequests()
//                // 对所有的请求都要进行认证
//                .anyRequest().authenticated();
//
//        // .formLogin()开启登录表单配置
//        // 还有一种简单的写法，那就是直接在上面使用 .and().formLogin 以链式来进行书写
//        http.formLogin()
//                // 指定进行认证的登录页面
//                .loginPage("/login1.html")
//                // 进行登录认证处理的路径
//                .loginProcessingUrl("/doLogin1")
//                // 使用自定义的 SavedRequestAwareAuthenticationSuccessHandler
//                .successHandler(mySavedSuccessHandler())
//                // 登录失败之后返回哪里？
//                .failureUrl("/login1.html")
//                // 登录页面传递的username和password变量的名字是啥
//                .usernameParameter("uname")
//                .passwordParameter("passwd")
//                // 与登录相关的页面和接口不做拦截，直接通过
//                .permitAll();
//
//        // 关闭CSRF防御功能。当然依然可以用 .and() 来写
//        http.csrf()
//                .disable();
//    }

    /**
     * 制定自定义的 SavedRequestAwareAuthenticationSuccessHandler
     *
     * @return
     */
    SavedRequestAwareAuthenticationSuccessHandler mySavedSuccessHandler() {
        SavedRequestAwareAuthenticationSuccessHandler handler = new SavedRequestAwareAuthenticationSuccessHandler();
        // 相当于之前 .defaultSuccessUrl() 配置的值
        handler.setDefaultTargetUrl("/test2");
        // 如果登录请求显式传递 target_page，则跳转到指定的页面
        handler.setTargetUrlParameter("target_page");
        return handler;
    }


    /**
     * configure 1.3
     * 直接使用自定义的 AuthenticationSuccessHandler接口的实现类
     * @param http
     * @throws Exception
     */
//    @Override
//    protected void configure(HttpSecurity http) throws Exception {
//        // .authorizeRequests()开启权限配置
//        http.authorizeRequests()
//                // 对所有的请求都要进行认证
//                .anyRequest().authenticated();
//
//        // .formLogin()开启登录表单配置
//        // 还有一种简单的写法，那就是直接在上面使用 .and().formLogin 以链式来进行书写
//        http.formLogin()
//                // 指定进行认证的登录页面
//                .loginPage("/login1.html")
//                // 进行登录认证处理的路径
//                .loginProcessingUrl("/doLogin1")
//                // 使用自定义的 SavedRequestAwareAuthenticationSuccessHandler
//                .successHandler(new DoLogin_1_Config_MyAuthenticationSuccessHandler())
//                // 登录失败之后返回哪里？此时会携带登录失败的原因
//                .failureUrl("/login1.html")
//                // 登录页面传递的username和password变量的名字是啥
//                .usernameParameter("uname")
//                .passwordParameter("passwd")
//                // 与登录相关的页面和接口不做拦截，直接通过
//                .permitAll();
//
//        // 关闭CSRF防御功能。当然依然可以用 .and() 来写
//        http.csrf()
//                .disable();
//    }


    /**
     * configure 1.4
     * 直接使用自定义的 successHandler接口 和 FailureHandler 接口
     * @param http
     * @throws Exception
     */
//    @Override
//    protected void configure(HttpSecurity http) throws Exception {
//        // .authorizeRequests()开启权限配置
//        http.authorizeRequests()
//                // 对所有的请求都要进行认证
//                .anyRequest().authenticated();
//
//        // .formLogin()开启登录表单配置
//        // 还有一种简单的写法，那就是直接在上面使用 .and().formLogin 以链式来进行书写
//        http.formLogin()
//                // 指定进行认证的登录页面
//                .loginPage("/login1.html")
//                // 进行登录认证处理的路径
//                .loginProcessingUrl("/doLogin1")
//                // 使用自定义的 SavedRequestAwareAuthenticationSuccessHandler
//                .successHandler(new DoLogin_1_Config_MyAuthenticationSuccessHandler())
//                // 登录失败之后返回哪里？此时会携带登录失败的原因
//                .failureHandler(new DoLogin_1_Config_MyAuthenticationFailureHandler())
//                // 登录页面传递的username和password变量的名字是啥
//                .usernameParameter("uname")
//                .passwordParameter("passwd")
//                // 与登录相关的页面和接口不做拦截，直接通过
//                .permitAll();
//
//        // 关闭CSRF防御功能。当然依然可以用 .and() 来写
//        http.csrf()
//                .disable();
//    }

    /**
     * configure 1.5
     * 添加logout相关配置
     * @param http
     * @throws Exception
     */
//    @Override
//    protected void configure(HttpSecurity http) throws Exception {
//        // .authorizeRequests()开启权限配置
//        http.authorizeRequests()
//                // 对所有的请求都要进行认证
//                .anyRequest().authenticated();
//
//        // .formLogin()开启登录表单配置
//        // 还有一种简单的写法，那就是直接在上面使用 .and().formLogin 以链式来进行书写
//        http.formLogin()
//                // 指定进行认证的登录页面
//                .loginPage("/login1.html")
//                // 进行登录认证处理的路径
//                .loginProcessingUrl("/doLogin1")
//                // 使用自定义的 SavedRequestAwareAuthenticationSuccessHandler
//                .successHandler(new DoLogin_1_Config_MyAuthenticationSuccessHandler())
//                // 登录失败之后返回哪里？此时会携带登录失败的原因
//                .failureHandler(new DoLogin_1_Config_MyAuthenticationFailureHandler())
//                // 登录页面传递的username和password变量的名字是啥
//                .usernameParameter("uname")
//                .passwordParameter("passwd")
//                // 与登录相关的页面和接口不做拦截，直接通过
//                .permitAll();
//
//        // 添加logout的相关配置
//        http.logout()
//                // 进行注销处理的路径
//                .logoutUrl("/doLogout1")
//                // 是否使session失效，默认为true
//                .invalidateHttpSession(true)
//                // 是否清除认证信息，默认为true
//                .clearAuthentication(true)
//                // 成功注销后的跳转地址
//                .logoutSuccessUrl("/login1.html");
//
//        // 关闭CSRF防御功能。当然依然可以用 .and() 来写
//        http.csrf()
//                .disable();
//    }

    /**
     * configure 1.6
     * 添加多路径匹配注销，且使用lambda表达式书写 logoutSuccessHandle
     *
     * @param http
     * @throws Exception
     */
//    @Override
//    protected void configure(HttpSecurity http) throws Exception {
//        // .authorizeRequests()开启权限配置
//        http.authorizeRequests()
//                // 对所有的请求都要进行认证
//                .anyRequest().authenticated();
//
//        // .formLogin()开启登录表单配置
//        // 还有一种简单的写法，那就是直接在上面使用 .and().formLogin 以链式来进行书写
//        http.formLogin()
//                // 指定进行认证的登录页面
//                .loginPage("/login1.html")
//                // 进行登录认证处理的路径
//                .loginProcessingUrl("/doLogin1")
//                // 使用自定义的 SavedRequestAwareAuthenticationSuccessHandler
//                .successHandler(new DoLogin_1_Config_MyAuthenticationSuccessHandler())
//                // 登录失败之后返回哪里？此时会携带登录失败的原因
//                .failureHandler(new DoLogin_1_Config_MyAuthenticationFailureHandler())
//                // 登录页面传递的username和password变量的名字是啥
//                .usernameParameter("uname")
//                .passwordParameter("passwd")
//                // 与登录相关的页面和接口不做拦截，直接通过
//                .permitAll();
//
//        // 添加logout的相关配置
//        http.logout()
//                // 多路径匹配注销，需要使用关键字 .logoutRequestMatcher()
//                .logoutRequestMatcher(
//                        // 表明下面的RequestMatcher之间是 OR的关系
//                        // 那自然就有 AndRequestMatcher，但是用的很少
//                        new OrRequestMatcher(
//                                // 如果向 /doLogout1 发送GET请求，允许注销
//                                // 如果向 /doLogout2 发送POST请求，也允许注销
//                                new AntPathRequestMatcher("/doLogout1", "GET"),
//                                new AntPathRequestMatcher("/doLogout2", "POST")
//                        ))
//                // 是否使session失效，默认为true
//                .invalidateHttpSession(true)
//                // 是否清除认证信息，默认为true
//                .clearAuthentication(true)
//                // 使用lambda表达式直接写接口实现类，毕竟只有唯一一个待实现方法
//                .logoutSuccessHandler(((httpServletRequest, httpServletResponse, authentication) -> {
//                    httpServletResponse.setContentType("application/json;charset=utf-8");
//                    HashMap<String, Object> map = new HashMap<>();
//                    map.put("status", 200);
//                    map.put("msg", "注销成功!");
//                    ObjectMapper om = new ObjectMapper();
//                    String s = om.writeValueAsString(map);
//                    httpServletResponse.getWriter().write(s);
//                }));
//
//        // 关闭CSRF防御功能。当然依然可以用 .and() 来写
//        http.csrf()
//                .disable();
//    }

    /**
     * configure 1.6
     * 添加多路径注销时，不同路径对应不同的接口实现类
     *
     * @param http
     * @throws Exception
     */
//    @Override
//    protected void configure(HttpSecurity http) throws Exception {
//        // .authorizeRequests()开启权限配置
//        http.authorizeRequests()
//                // 对所有的请求都要进行认证
//                .anyRequest().authenticated();
//
//        // .formLogin()开启登录表单配置
//        // 还有一种简单的写法，那就是直接在上面使用 .and().formLogin 以链式来进行书写
//        http.formLogin()
//                // 指定进行认证的登录页面
//                .loginPage("/login1.html")
//                // 进行登录认证处理的路径
//                .loginProcessingUrl("/doLogin1")
//                // 使用自定义的 SavedRequestAwareAuthenticationSuccessHandler
//                .successHandler(new DoLogin_1_Config_MyAuthenticationSuccessHandler())
//                // 登录失败之后返回哪里？此时会携带登录失败的原因
//                .failureHandler(new DoLogin_1_Config_MyAuthenticationFailureHandler())
//                // 登录页面传递的username和password变量的名字是啥
//                .usernameParameter("uname")
//                .passwordParameter("passwd")
//                // 与登录相关的页面和接口不做拦截，直接通过
//                .permitAll();
//
//        // 添加logout的相关配置
//        http.logout()
//                // 多路径匹配注销，需要使用关键字 .logoutRequestMatcher()
//                .logoutRequestMatcher(
//                        // 表明下面的RequestMatcher之间是 OR的关系
//                        // 那自然就有 AndRequestMatcher，但是用的很少
//                        new OrRequestMatcher(
//                                // 如果向 /doLogout1 发送GET请求，允许注销
//                                // 如果向 /doLogout2 发送POST请求，也允许注销
//                                new AntPathRequestMatcher("/doLogout1", "GET"),
//                                new AntPathRequestMatcher("/doLogout2", "POST")
//                        ))
//                // 是否使session失效，默认为true
//                .invalidateHttpSession(true)
//                // 是否清除认证信息，默认为true
//                .clearAuthentication(true)
//                // 使用lambda表达式直接写接口实现类，毕竟只有唯一一个待实现方法
//                .defaultLogoutSuccessHandlerFor((httpServletRequest, httpServletResponse, authentication) -> {
//                    httpServletResponse.setContentType("application/json;charset=utf-8");
//                    HashMap<String, Object> map = new HashMap<>();
//                    map.put("status", 200);
//                    map.put("msg", "使用 /doLogout1 注销成功!");
//                    ObjectMapper om = new ObjectMapper();
//                    String s = om.writeValueAsString(map);
//                    httpServletResponse.getWriter().write(s);
//                }, new AntPathRequestMatcher("/doLogout1", "GET"))
//                .defaultLogoutSuccessHandlerFor((httpServletRequest, httpServletResponse, authentication) -> {
//                    httpServletResponse.setContentType("application/json;charset=utf-8");
//                    HashMap<String, Object> map = new HashMap<>();
//                    map.put("status", 200);
//                    map.put("msg", "使用 /doLogout2 注销成功!");
//                    // 将 POST 的表单提取出来
//                    StringBuilder data = new StringBuilder();
//                    String line = null;
//                    BufferedReader reader = null;
//                    try {
//                        reader = httpServletRequest.getReader();
//                        while (null != (line = reader.readLine())) {
//                            data.append(line);
//                        }
//                    } catch (Exception e) {
//                        e.printStackTrace();
//                    }
//                    //返回的是JSON对象
//                    String JSONString = data.toString();
//                    map.put("data", JSONString);
//
//                    ObjectMapper om = new ObjectMapper();
//                    String s = om.writeValueAsString(map);
//                    httpServletResponse.getWriter().write(s);
//                }, new AntPathRequestMatcher("/doLogout2", "POST"));
//        // 关闭CSRF防御功能。当然依然可以用 .and() 来写
//        http.csrf()
//                .disable();
//    }

    /**
     * configure 1.7
     * 放行验证码求情
     */
//    @Override
//    protected void configure(HttpSecurity http) throws Exception {
//        // .authorizeRequests()开启权限配置
//        http.authorizeRequests()
//                // 匹配该路径并放行
//                .antMatchers("/vcode.jpg").permitAll()
//                // 对所有的请求都要进行认证
//                .anyRequest().authenticated();
//
//        // .formLogin()开启登录表单配置
//        // 还有一种简单的写法，那就是直接在上面使用 .and().formLogin 以链式来进行书写
//        http.formLogin()
//                // 指定进行认证的登录页面
//                .loginPage("/login1.html")
//                // 进行登录认证处理的路径
//                .loginProcessingUrl("/doLogin1")
//                // 使用自定义的 SavedRequestAwareAuthenticationSuccessHandler
//                .successHandler(new DoLogin_1_Config_MyAuthenticationSuccessHandler())
//                // 登录失败之后返回哪里？此时会携带登录失败的原因
//                .failureHandler(new DoLogin_1_Config_MyAuthenticationFailureHandler())
//                // 登录页面传递的username和password变量的名字是啥
//                .usernameParameter("uname")
//                .passwordParameter("passwd")
//                // 与登录相关的页面和接口不做拦截，直接通过
//                .permitAll();
//
//        // 添加logout的相关配置
//        http.logout()
//                // 多路径匹配注销，需要使用关键字 .logoutRequestMatcher()
//                .logoutRequestMatcher(
//                        // 表明下面的RequestMatcher之间是 OR的关系
//                        // 那自然就有 AndRequestMatcher，但是用的很少
//                        new OrRequestMatcher(
//                                // 如果向 /doLogout1 发送GET请求，允许注销
//                                // 如果向 /doLogout2 发送POST请求，也允许注销
//                                new AntPathRequestMatcher("/doLogout1", "GET"),
//                                new AntPathRequestMatcher("/doLogout2", "POST")
//                        ))
//                // 是否使session失效，默认为true
//                .invalidateHttpSession(true)
//                // 是否清除认证信息，默认为true
//                .clearAuthentication(true)
//                // 使用lambda表达式直接写接口实现类，毕竟只有唯一一个待实现方法
//                .defaultLogoutSuccessHandlerFor((httpServletRequest, httpServletResponse, authentication) -> {
//                    httpServletResponse.setContentType("application/json;charset=utf-8");
//                    HashMap<String, Object> map = new HashMap<>();
//                    map.put("status", 200);
//                    map.put("msg", "使用 /doLogout1 注销成功!");
//                    ObjectMapper om = new ObjectMapper();
//                    String s = om.writeValueAsString(map);
//                    httpServletResponse.getWriter().write(s);
//                }, new AntPathRequestMatcher("/doLogout1", "GET"))
//                .defaultLogoutSuccessHandlerFor((httpServletRequest, httpServletResponse, authentication) -> {
//                    httpServletResponse.setContentType("application/json;charset=utf-8");
//                    HashMap<String, Object> map = new HashMap<>();
//                    map.put("status", 200);
//                    map.put("msg", "使用 /doLogout2 注销成功!");
//                    // 将 POST 的表单提取出来
//                    StringBuilder data = new StringBuilder();
//                    String line = null;
//                    BufferedReader reader = null;
//                    try {
//                        reader = httpServletRequest.getReader();
//                        while (null != (line = reader.readLine())) {
//                            data.append(line);
//                        }
//                    } catch (Exception e) {
//                        e.printStackTrace();
//                    }
//                    //返回的是JSON对象
//                    String JSONString = data.toString();
//                    map.put("data", JSONString);
//
//                    ObjectMapper om = new ObjectMapper();
//                    String s = om.writeValueAsString(map);
//                    httpServletResponse.getWriter().write(s);
//                }, new AntPathRequestMatcher("/doLogout2", "POST"));
//        // 关闭CSRF防御功能。当然依然可以用 .and() 来写
//        http.csrf()
//                .disable();
//    }

    /**
     * configure 1.8 多过滤器链
     */
//    @Override
//    protected void configure(HttpSecurity http) throws Exception {
//
//        // 上面只是进入到该拦截器，但是没有说到底是放行还是有认证要求
//        http.antMatcher("/test1/**")
//                .authorizeRequests()
//                .anyRequest().authenticated();
//
//        // .formLogin()开启登录表单配置
//        // 还有一种简单的写法，那就是直接在上面使用 .and().formLogin 以链式来进行书写
//        http.formLogin()
//                // 指定进行认证的登录页面
//                .loginPage("/login1.html")
//                // 进行登录认证处理的路径
//                .loginProcessingUrl("/test1/doLogin1")
//                // 直连登录页面成功后的响应地址
//                .defaultSuccessUrl("/test1")
//                // 登录失败之后返回哪里？
//                .failureUrl("/login1.html")
//                // 登录页面传递的username和password变量的名字是啥
//                .usernameParameter("uname")
//                .passwordParameter("passwd")
//                // 与登录相关的页面和接口不做拦截，直接通过
//                .permitAll();
//
//
//        // 关闭CSRF防御功能。当然依然可以用 .and() 来写
//        http.csrf()
//                .disable();
//    }

    /**
     * configure 1.9
     * 添加自定义Filter，此时既可以向 /test1/doLogin1 发送表单登录请求，也可以向 /test1/doLogin1_Json 发送Json登录请求
     *
     * @param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {

        // 上面只是进入到该拦截器，但是没有说到底是放行还是有认证要求
        http.antMatcher("/test1/**")
                .authorizeRequests()
                .antMatchers("/test1/doLogin1_Json").permitAll()
                .antMatchers("/vcode.jpg").permitAll()
                .anyRequest().authenticated();
//        http.authorizeRequests()
//                .antMatchers("/test1/doLogin1_Json").permitAll()
//                .antMatchers("/vcode.jpg").permitAll()
//                .anyRequest().authenticated();

        // .formLogin()开启登录表单配置
        // 还有一种简单的写法，那就是直接在上面使用 .and().formLogin 以链式来进行书写
        http.formLogin()
                // 指定进行认证的登录页面
                .loginPage("/login1.html")
                // 进行登录认证处理的路径
                .loginProcessingUrl("/test1/doLogin1")
                // 直连登录页面成功后的响应地址
                .defaultSuccessUrl("/test1")
                // 登录失败之后返回哪里？
                .failureUrl("/login1.html")
                // 登录页面传递的username和password变量的名字是啥
                .usernameParameter("uname")
                .passwordParameter("passwd")
                // 与登录相关的页面和接口不做拦截，直接通过
                .permitAll();

        // 使用 addFilterAt()将自定义过滤器添加到当前的过滤器链中
        // 可以认为 addFilterAt() 和 addFilterBefore() 是一样的功能
        http.addFilterAt(this.myJsonLoginFilter(), UsernamePasswordAuthenticationFilter.class);

        // 关闭CSRF防御功能。当然依然可以用 .and() 来写
        http.csrf()
                .disable();
    }


    /**
     * configure 2.1
     * 在入参为 AuthenticationManagerBuilder configure的重载方法中，将自定义的 UserDetailsService接口实现类提供给 AuthenticationManagerBuilder
     * 此时自定义的UserDetailsService 为基于内存的 InMemoryUserDetailsManager
     * @param auth
     * @throws Exception
     */
//    @Override
//    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//        // 创建一个新的 InMemoryUserDetailsManager
//        InMemoryUserDetailsManager memoryUserDetailsManager = new InMemoryUserDetailsManager();
//        // 为Manager添加新的用户。注意这里添加的用户必须是 UserDetails接口的实现类，这里使用SSY提供的默认实现类 User
//
//        // 不使用User自带的builder方法，使用构造函数
//        // 构造函数需要自己构建权限列表
//        ArrayList<GrantedAuthority> authorities = new ArrayList<>();
//        authorities.add(new SimpleGrantedAuthority("ROLE_admin"));
//        // 注意 5.4版本之后，密码必须加上编码方式前缀
//        memoryUserDetailsManager.createUser(
//                new User("zhangsan",
//                        "{noop}123",
//                        authorities));
//
//        // 使用User自带的builder方法(是一个静态方法，但是返回的是 User实例)
//        memoryUserDetailsManager.createUser(
//                User.builder().
//                        username("lisi").
//                        password("{noop}123").
//                        roles("user").
//                        build()
//        );
//
//        // 将自定义的 UserDetailsService接口实现类提供给 AuthenticationManagerBuilder
//        auth.userDetailsService(memoryUserDetailsManager);
//    }


    /**
     * configure 2.2
     * 在入参为 AuthenticationManagerBuilder configure的重载方法中，将自定义的 UserDetailsService接口实现类提供给 AuthenticationManagerBuilder
     * 此时自定义的UserDetailsService 为自行实现的基于数据库的 UserEntityServiceImpl
     *
     * @param auth
     * @throws Exception
     */
//    @Autowired
//    UserEntityServiceImpl userEntityServiceImpl;
//
//    @Override
//    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//        // 注意，UserEntityServiceImpl可以看做是全局的Service层
//        // 注意这里必须使用 @Autowired，而不能使用 new UserEntityServiceImpl
//        // 否则在 userEntityServiceImpl 中自动注入的那几个 Service层会变成空null
//        auth.userDetailsService(userEntityServiceImpl);
//    }


    /**
     * configure 2.3
     * 配置多个 AuthenticationProvider
     */
    // 自动注入 UserDetailsService的一个自定义实现类--UserEntityServiceImpl
    @Autowired
    UserEntityServiceImpl userEntityServiceImpl;
    // 自动注入 UserDetailsService的一个自定义实现类--InMemoryServiceImpl_1
    @Autowired
    InMemoryServiceImpl_1 inMemoryServiceImpl_1;

    // 和把 InMemoryServiceImpl_1写成 Service层组件实现的功能一模一样。
    // 这里脱离 Service层，那么就得手动加上 @Bean
    @Bean
    UserDetailsService InMemoryServiceImpl_2() {
        // 和 _1 一样自定义 loadUserByUsername的异常内容。
        // 这里的写法会吧？直接创建新类的时候用@Override覆写，不想写了的时候直接 super.
        InMemoryUserDetailsManager inMemoryServiceImpl_2 = new InMemoryUserDetailsManager() {
            @Override
            public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
                if (!username.equals("inMemory_Bean")) {
                    throw new UsernameNotFoundException("InMemoryServiceImpl_2 未找到用户" + username);
                }
                return super.loadUserByUsername(username);
            }
        };

        inMemoryServiceImpl_2.createUser(User.builder().
                username("inMemory_Bean").
                password("{noop}123").
                roles("user").
                build());
        return inMemoryServiceImpl_2;
    }

    // 和 configure2.1 configure2.2 不同，这里不再是为默认的 AuthenticationManager中添加 UserDetailsService，这里之间把顶层的 AuthenticationManager都改了
    // authenticationManagerBean()是一个Override方法，由SSY框架提供。
    // 调用 这个方法返回的 AuthenticationManager 会被SSY自动绑定到默认的 UsernamePasswordAuthenticationFilter上。
    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        // 创建一个 AuthenticationProvider，声明使用自定义异常，并将其 UserDetailsService设置为自动注入的 inMemoryServiceImpl_1
        // DaoAuthenticationProvider dao1 = new DaoAuthenticationProvider();
        // 添加验证码校验
        DaoAuthenticationProvider dao1 = new DaoAuthenticationProvider() {
            @Override
            public Authentication authenticate(Authentication authentication) throws AuthenticationException {
                // 如果不单独写出去一个 DaoAuthenticationProvider，直接使用这种方式去覆写也毫无问题
                ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
                assert requestAttributes != null;
                HttpServletRequest httpServletRequest = requestAttributes.getRequest();
                if (!httpServletRequest.getContentType().equalsIgnoreCase(MediaType.APPLICATION_JSON_VALUE)
                        && !httpServletRequest.getContentType().equalsIgnoreCase(MediaType.APPLICATION_JSON_UTF8_VALUE)) {
                    // 如果登录方式是JSON则不进行验证码判断
                    String kaptcha = httpServletRequest.getParameter("kaptcha");
                    String kaptchaSession = (String) httpServletRequest.getSession().getAttribute("kaptcha");
                    if (kaptcha == null || !kaptcha.equals(kaptchaSession)) {
                        throw new AuthenticationServiceException("验证码错误");
                    }
                }
                return super.authenticate(authentication);
            }
        };
        dao1.setHideUserNotFoundExceptions(false);
        dao1.setUserDetailsService(inMemoryServiceImpl_1);

        // 创建一个 AuthenticationProvider，由于inMemoryServiceImpl_1是在方法上做的Bean，所以得用 InMemoryServiceImpl_2()，而不是向上面那个一样直接传递Bean实例
        // DaoAuthenticationProvider dao2 = new DaoAuthenticationProvider();
        // 添加验证码校验
        MyDaoAuthenticationProvider dao2 = new MyDaoAuthenticationProvider();
        dao2.setHideUserNotFoundExceptions(false);
        dao2.setUserDetailsService(InMemoryServiceImpl_2());

        // 创建一个 AuthenticationProvider，传入 userEntityServiceImpl
        // DaoAuthenticationProvider dao3 = new DaoAuthenticationProvider();
        // 添加验证码校验
        MyDaoAuthenticationProvider dao3 = new MyDaoAuthenticationProvider();
        dao3.setHideUserNotFoundExceptions(false);
        dao3.setUserDetailsService(userEntityServiceImpl);

        // 现在已经有了多个 AuthenticationProvider了，然后就需要把他们放到 AuthenticationManager中统一管理
        // 这里还是直接使用SSY默认的实现类 ProviderManager
        // ProviderManager没有setAuthenticationProvider的方法，只能在构造方法中传入要set的多个 AuthenticationProvider
        // 注意，这里 AuthenticationProvider的书写顺序决定了遍历的顺序。如果最后认证失败，只会抛出最后一个 AuthenticationProvider中定义的异常
        ProviderManager providerManager = new ProviderManager(dao1, dao2, dao3) {
            // 这里是手动为 ProviderManager添加了一个新方法--为 ProviderManager添加新的 AuthenticationProvider
            public void addAuthenticationProvider(AuthenticationProvider authenticationProvider) {
                this.getProviders().add(authenticationProvider);
            }
        };
        return providerManager;
    }


    // 将 JsonLoginFilter制作成 Bean，主要要在这里完成 AuthenticationManager的绑定
    @Bean
    JsonLoginFilter myJsonLoginFilter() throws Exception {
        JsonLoginFilter myJsonLoginFilter = new JsonLoginFilter();
        // 就和表单登录的 .loginProcessingUrl("/test1/doLogin1")一样，这里就是规定该Filter的处理路径
        myJsonLoginFilter.setFilterProcessesUrl("/test1/doLogin1_Json");
        // 将上面制作的哪个 AuthenticationManager 与当前 Filter进行绑定
        myJsonLoginFilter.setAuthenticationManager(this.authenticationManagerBean());
        // 设置json中username和password字段的标识
        myJsonLoginFilter.setUsernameParameter("uname");
        myJsonLoginFilter.setPasswordParameter("passwd");
        // 同样可以为自定义Filter设置成功/失败的 Handler
        myJsonLoginFilter.setAuthenticationSuccessHandler(new DoLogin_1_Config_MyAuthenticationSuccessHandler());
        myJsonLoginFilter.setAuthenticationFailureHandler(new DoLogin_1_Config_MyAuthenticationFailureHandler());
        return myJsonLoginFilter;
    }
}
